#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <iostream>
#include <string>
#include <cstring>  
#include <list>
#include "message.hpp"

using namespace std;
#define QUEUESIZE 5

struct server {
	in_addr_t ip;
	int port;
	int sid;
	server(in_addr_t m_ip, int m_port, int m_sid){
		ip = m_ip;
		port = m_port;
		sid = m_sid;
	}
};

struct function {
	char name[20];
	int argnum;
	int *argtype;
	list<server*> servers;
};

list<function*> functionlist;
list<server*> serverlist;

// if the server exists, return the address
// else add new server and return it
server *find_server(in_addr_t ip, int port, int sid){
	server *newServer = NULL;
	server *tempServer= NULL;
	list<server*>::iterator f;
	for(f = serverlist.begin(); f != serverlist.end(); f++){
		tempServer = *f;
		if(ip == tempServer->ip && port == tempServer->port && sid == tempServer->sid)
			return tempServer;
	}
	newServer = new server(ip, port, sid);
	serverlist.push_back(newServer);
	return newServer;
}

// look up if the function exist, if yes return the first server else NULL
server *look_up_server(char name[], int argType[], int argnum){
	list<function*>::iterator f;
	function *temp_fcn = NULL;
	bool hasfunction = false;
	server *result = NULL;
	for(f = functionlist.begin(); f != functionlist.end(); f++){
		temp_fcn = *f;
        cout << "name " << name << " stored name " << temp_fcn->name << endl;
        cout << "argnum " << argnum << " stored argnum " << temp_fcn->argnum << endl; 
		if( strcmp(name,temp_fcn->name) != 0) continue;
		if( argnum != temp_fcn->argnum ) continue;
        cout << "name and argnum equals" << endl;
		hasfunction = true;
		for(int i=0; i< argnum; i++){
            cout << "arg: " << argType[i] << " storedarg " << temp_fcn->argtype[i] << endl;
			if(argType[i] != temp_fcn->argtype[i]){
				hasfunction = false;
				break;
			}
		}
		if(hasfunction){
            cout << "has function" << endl;
			result = temp_fcn->servers.front();
			temp_fcn->servers.pop_front();
			temp_fcn->servers.push_back(result);
			return result;
		}
	}
	return NULL;
}

void add_function(char name[], int argType[], int argnum, server* target_server){
	list<function*>::iterator f;
	function *temp_fcn = NULL;
	bool hasfunction = false;
	for(f = functionlist.begin(); f != functionlist.end(); f++){
		temp_fcn = *f;
		if( name != temp_fcn->name) continue;
		if( argnum != temp_fcn->argnum ) continue;
		hasfunction = true;
		
		for(int i=0; i< argnum; i++){
			if(argType[i] != temp_fcn->argtype[i]){
				hasfunction = false;
				break;
			}
		}
		if(hasfunction){
			list<server*> fcn_servers = temp_fcn->servers;
			list<server*>::iterator i;
			for(i = fcn_servers.begin(); i != fcn_servers.end(); i++){
				if(target_server == *i) return;
			}
			fcn_servers.push_back(target_server);
			break;
		}
	}
	if(!hasfunction){
		function *newfunction = new function();
		strcpy(newfunction->name,name);
		newfunction->argnum = argnum;
		newfunction->argtype = new int[argnum];
        for(int i=0; i<argnum; i++){
            newfunction->argtype[i] = argType[i];
        }
		newfunction->servers.push_back(target_server);
		functionlist.push_back(newfunction); 
	}
}

void print_data(){
	cout << "server list:" << endl;
	list<server*>::iterator f;
	int counter = 0;
	server *tempServer= NULL;
	for(f = serverlist.begin(); f != serverlist.end(); f++){
		tempServer = *f;
		cout << "server " << counter << ":"<< endl;
		cout << "ip: "<<tempServer->ip << "||";
		cout << "port: "<<tempServer->port << "||";
		cout << "sid: "<<tempServer->sid << "||" << endl;
		counter++;
	}
	
	cout << "function list:" << endl;
	list<function*>::iterator g;
	function *temp_fcn = NULL;
	counter = 0;
	for(g = functionlist.begin(); g != functionlist.end(); g++){
		temp_fcn = *g;
		cout << "function " << counter << ":" << endl;
		cout << "name: " <<temp_fcn->name << "||";
		cout << "number of server: " << temp_fcn->servers.size() << endl;
		counter++;
	}
}

int establish()
{
    int socket_fd;
    struct sockaddr_in server;
    
    /*clear out address*/
    memset(&server, 0, sizeof(sockaddr_in));
    
    /*set up the host addr type, port number and ip address of server*/
    server.sin_family = AF_INET;
    server.sin_port = 0;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    
    /*create a socket*/
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if( socket_fd < 0 ){
		cerr << "fail to create socket" << endl;
    	exit(-1);
	}
    
    /*rebind multi-connections*/
    int yes = 1;
    if((setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) < 0 ){
        cerr << "fail to set socket option" << endl;
        exit(-1);
    }
    
    /*bind the socket*/
    if( bind(socket_fd, (struct sockaddr*)&server, sizeof(struct sockaddr)) < 0 ){
        close(socket_fd);
		cerr << "fail to bind the socket" << endl;
        exit(-1);
    }
    
    /*listen on the socket*/
    if( listen(socket_fd, QUEUESIZE) < 0 ){
		cerr << "fail to listen on the socket" << endl;
        exit(-1);
    }
    
    /*print server address*/
    char hostname[256];
    gethostname(hostname, 256);
    cout << "SERVER_ADDRESS " << hostname << endl;
    
    /*print port number*/
    socklen_t server_size = sizeof(server);
    getsockname(socket_fd, (struct sockaddr *)&server, &server_size);
    cout << "SERVER_PORT " << ntohs(server.sin_port) << endl;
    
    return socket_fd;
}

int main(){
    int socket_fd = establish();
    struct sockaddr_in client;
    socklen_t client_size;
	int result;
    int new_fd;
	fd_set readset;
    int fds[QUEUESIZE] = {0};
    /*accept connection from client, and receive or send msg to client*/
	for(;;) {
        /*select the client*/
        FD_ZERO(&readset);
        FD_SET(socket_fd, &readset);
		for(int i = 0; i < QUEUESIZE; i++){
            if(fds[i] > 0){ FD_SET(fds[i], &readset); }
        } 
        result = select(FD_SETSIZE, &readset, NULL, NULL, NULL);
        if(result < 0){
            cerr << "fail to select readable sockets" << endl;
            //exit(-1);
        }
        
        /*accept the connection from client*/
        if(FD_ISSET(socket_fd, &readset) ){
            client_size = sizeof(client);
            new_fd = accept(socket_fd,(struct sockaddr*)&client,&client_size);
            if( new_fd < 0 ){
                cerr << "cilent closed the socket" << endl;
                //exit(-1);
            }
            int j;
            for(j = 0; j < QUEUESIZE; j++){
                if(fds[j] == 0){
                    fds[j] = new_fd;
                    break;
                }
            }
            if( j == QUEUESIZE )
                close(new_fd);
		}
        
        /*provide service to client */
        for(int i=0; i<QUEUESIZE; i++){
            if(FD_ISSET(fds[i], &readset)){
                
                /*clean up buffer*/
                //memset(buffer,0,sizeof(buffer));
                
                /*receive the size of msg*/
				int msg_length;
				int size = recv(fds[i],&msg_length,sizeof(int),MSG_WAITALL);
                if( size != sizeof(int)){
					close(fds[i]);
					fds[i] = 0;
					FD_CLR(fds[i], &readset); // remove fd
					cerr << "fail to receive msg length" << endl;
					continue;
				}
				//std::cout << "msg size: " << msg_length << std::endl;
				
				/*receove type of msg*/
				int msg_type;
				size = recv(fds[i], &msg_type, sizeof(int),MSG_WAITALL);
				if( size != sizeof(int)){
					close(fds[i]);
					fds[i] = 0;
					FD_CLR(fds[i], &readset); // remove fd
					cerr << "fail to receive type" << endl;
					continue;
				}
				//std::cout << "msg type: " << msg_type << std::endl;
				
				/*receive the msg*/
				int read_offset = 0;
				char buffer[msg_length];
				memset(buffer,0,sizeof(buffer)); // clean buffer
				bool success = true;
				while(read_offset < msg_length){
					size = recv(fds[i], buffer, msg_length, MSG_WAITALL);
					if(size < 0){
						close(fds[i]);
						fds[i] = 0;
						FD_CLR(fds[i], &readset);
						success = false;
						break;
					}
					read_offset += size;
				}
				if(!success) continue;
				//cout << "got msg: " << endl;
				
				in_addr_t ip;
				int port;
				int argtype_length;
				server *target_server = NULL;
				switch(msg_type) {
					case 1: // local request
						// name, argtypes
						char name1[20];
						argtype_length = (int)((msg_length-20)/sizeof(int));
						int argTypes1[argtype_length];
						myUnmarshall(msg_type, buffer, msg_length, name1, argTypes1, NULL, NULL, NULL, 0);
						cout << "buffersize: " << msg_length << endl;
						cout << "name: " << name1 << endl;
						cout << "typesize: " << argtype_length << endl;
						for(int k=0; k < argtype_length; k++){
							cout << "type" << k << ": " << argTypes1[k];
						}
						cout << endl;
						target_server = look_up_server(name1, argTypes1,argtype_length);
						
						int replytype;
						char *reply; 
						int replysize;
						if(target_server){
							ip = target_server->ip;
							port = target_server->port;
							replytype = LOC_SUCCESS;
							reply = myMarshall(replytype, &replysize, NULL, NULL, NULL,ip,port,0);
							send(fds[i],&replysize,sizeof(int),0);
							send(fds[i],&replytype,sizeof(int),0);
							send(fds[i],reply,replysize,0);
						}
						else{
							replytype = LOC_FAILURE;
							reply = myMarshall(replytype, &replysize,NULL,NULL,NULL,0,0,-1);
							send(fds[i],&replysize,sizeof(int),0);
							send(fds[i],&replytype,sizeof(int),0);
							send(fds[i],reply,replysize,0);
						}
						close(fds[i]);
						fds[i] = 0;
						continue;
					break;
					case 4: //register
						//ip, port, name, argtypes
						char name4[20];
						argtype_length = (int)((msg_length-sizeof(in_addr_t)-4-20)/sizeof(int));
						int argTypes4[argtype_length];
				
						myUnmarshall(msg_type, buffer, msg_length,name4, argTypes4, NULL, &ip, &port, 0);
						/*cout << "ip: " << ip << endl;
						cout << "port: " << port << endl;
						cout << "name:" << name4 << endl;
						*/
						for(int k=0; k < argtype_length; k++){
							cout << "type" << k << ": " << argTypes4[k];
						}
						cout << endl;
						
						
						target_server = find_server(ip, port, fds[i]);
						add_function(name4, argTypes4, argtype_length, target_server);
					break;
					
					case 8: // termination
						msg_length = 0;
						list<server*>::iterator f;
						for(f = serverlist.begin(); f != serverlist.end(); f++){
							send((*f)->sid, &msg_length, sizeof(int),0);
							send((*f)->sid, &msg_type, sizeof(int),0);
						}
						return 0;
				}

            }
        }
	}
	
    /*close the socket*/
	close(socket_fd);
}
